using UnityEngine;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Networking;
using SimpleJSON;
#if UNITY_EDITOR
using UnityEditor;
#endif


namespace Gj
{
  public class AppInfo
  {
    public string version;

    public string iosUrl;

    public string androidUrl;

    public string date;

    public int androidSize;
  }

  public static class BundleBuild
  {
    public const string iOS = "iOS";
    public const string Android = "Android";
    public const string OSX = "StandaloneOSX";
    public const string WIN = "StandaloneWindows";
    public const string LINUX = "StandaloneLinux";
  }

  public class BundleInfo
  {
    public string name = "";
    public string fullName = "";
    public bool stream = true;
    public bool download = false;
    public string version = "";
  }

  public class DownloadInfo
  {
    public string name = "";
    public string hash = "";
    public string version = "";
  }

  public class NamespaceInfo
  {
    // 加载过bundle
    public bool loaded = false;
    // 读取stream数据
    public bool stream = false;

    public string ns = "";
    private AssetBundle manifestBundle;
    private AssetBundleManifest manifest;
    private string version = "";
    public string url = "";
    public int size = 0;
    public List<string> loadingBundles = new List<string>();
      
    public string lastVersion = "";
    public string lastUrl = "";
    public int lastSize = 0;
    public string Version
    {
      get
      {
        return version;
      }
      set
      {
        version = value;
        Hash = Hash128.Parse(version);
      }
    }
    internal Hash128 Hash;

    public Dictionary<string, BundleInfo> bundles = new Dictionary<string, BundleInfo>();
    public Dictionary<string, DownloadInfo> files = new Dictionary<string, DownloadInfo>();

    internal IEnumerator InitBundle()
    {
      string uri;
      if (stream)
      {
        uri = GetStream(GetName(Resource.Build));
      }
      else
      {
        uri = GetUrl(GetName(Resource.Build));
      }
      LogTools.Info("Init Bundle Manifest(" + ns + "):" + uri+", hash:"+Hash.ToString());
      Hash = Hash128.Parse(Version);
      UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, Hash, 0);
      yield return request.SendWebRequest();
      Debug.Log(request.error);
      AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
      manifestBundle = bundle;
      LoadAllBundleName();
    }

    public IEnumerator Download(string name, Action<byte[]> callback)
    {
      string uri;
      DownloadInfo info;
      string hash = "";
      if (!stream && files.TryGetValue(name, out info))
      {
        var path = Path.Combine(Application.persistentDataPath, "download", info.hash);
        if (info.version == version)
        {
          LogTools.Info("Download file in cache(" + ns + "):" + path);
          callback(FileTools.LoadByteFile(path));
          yield break;
        }
        else
        {
          LogTools.Info("Download file clear cache(" + ns + "):" + path);
          // 删除过期文件
          try
          {
            File.Delete(path);
          }
          catch (Exception e)
          {
            // 忽略删除错误
          }
        }
      }

      if (stream)
      {
        uri = GetStream(name);
        LogTools.Info("Download From Stream(" + ns + "):" + uri);
      }
      else
      {
        FileTools.CreateFolder(Application.persistentDataPath, "download");
        uri = GetUrl(name);
        LogTools.Info("Download(" + ns + "):" + uri);
      }
      UnityWebRequest request = UnityWebRequest.Get(uri);
      yield return request.SendWebRequest();
      byte[] data = null;
      if (request.isDone)
      {
        data = request.downloadHandler.data;
        if (!stream)
        {
          hash = Hash128.Parse(ns + "/" + name + "/" + version).ToString();
          var path = Path.Combine(Application.persistentDataPath, "download", hash);
          FileTools.SaveByteFile(path, data);
          info = new DownloadInfo
          {
            name = name,
            hash = hash,
            version = version,
          };
          files.Remove(name);
          files.Add(name, info);
          LogTools.Info("Download file save cache(" + ns + "):" + path);
        }
      }
      callback(data);
    }

    public IEnumerator Update(bool auto, Action<int, string> callback)
    {
      // 更新清单文件
      string uri = GetUrl(GetName(Resource.Build));
      LogTools.Info("Download Bundle Manifest(" + ns + "):" + uri);
      if (manifestBundle != null)
      {
        manifestBundle.Unload(true);
      }
      UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, Hash, 0);
      yield return request.SendWebRequest();
      AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
      if (bundle == null)
      {
        LogTools.Error("Bundle Manifest not exist(" + ns + "):" + uri);
        yield break;
      }
      manifestBundle = bundle;
      stream = false;
      var updateList = LoadAllBundleName();

      if (auto)
      {
        // 自动下载全部资源包
        var e = updateList.GetEnumerator();
        var length = updateList.Length;
        while (e.MoveNext())
        {
          var bundleName = e.Current.ToString();
          yield return LoadBundleInline(bundleName, (n, b) =>
          {

          });
          callback(length, bundleName);
          length -= 1;
        }
      }
    }

    public string[] LoadAllBundleName()
    {
      manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
      string[] bundleList = manifest.GetAllAssetBundles();
      List<string> updateList = new List<string>();
      for (var i = 0; i < bundleList.Length; i++)
      {
        var file = bundleList[i];
        var ext = Path.GetExtension(file);
        var name = Path.GetFileNameWithoutExtension(file);
        var tmp = name.Split('_');
        string shortName;
        if (tmp.Length > 1)
        {
          var tt = tmp.ToList();
          tt.RemoveAt(tt.Count-1);
          shortName = string.Join('_', tt);
        }
        else
        {
          shortName = name;
        }
        // 变体为一个新的bundle
        if (ext != string.Empty)
        {
          shortName += ext;
        }

        shortName = shortName;
        name = GetBundleName(shortName, ext);

        BundleInfo bundleInfo;
        if (bundles.TryGetValue(name, out bundleInfo))
        {
          if (file == bundleInfo.fullName)
          {
            LogTools.Info("No Change map:" + name + "=>" + file);
            if (bundleInfo.stream || bundleInfo.download)
            {
              continue;
            }
            // 没下载过则更新信息
          }
          else
          {
            LogTools.Info("Update map:" + name + "=>" + file);
          }
        }
        else
        {
          bundleInfo = new BundleInfo();
          bundles.Add(name, bundleInfo);
          LogTools.Info("Add map:" + name + "=>" + file);
        }
        updateList.Add(name);
        bundleInfo.name = name;
        bundleInfo.fullName = file;
        bundleInfo.version = version;
        bundleInfo.download = false;
        bundleInfo.stream = stream;
      }
      loaded = true;
      return updateList.ToArray();
    }

    internal string GetUrl(string name)
    {
      // 开启ns，stream读取加上ns
      // 外部链接上有ns
      //	        name = ns + "/" + name;

      return Path.Combine(url, name);
    }

    internal string GetName(string bundleName)
    {
      return Resource.LocalBundle + "/" + Resource.Build + "/" + bundleName;
    }

    internal string GetStream(string name)
    {
      if (Resource.NamespaceStatus)
      {
        // 开启ns，stream读取加上ns
        name = ns + "/" + name;
      }
      name = Resource.LocalNamespace + "/" + name;
#if UNITY_ANDROID && !UNITY_EDITOR
            string uri = Application.streamingAssetsPath + "/" + name;
#else
      string uri = "file://" + Application.streamingAssetsPath + "/" + name;
      // uri = Application.streamingAssetsPath + "/" + name;
#endif
      return uri;
    }

    internal IEnumerator LoadBundle(string bundleName, string variant, Action<int, AssetBundle> callback)
    {
      bundleName = GetBundleName(bundleName, variant);
      return LoadBundleInline(bundleName, callback);
    }

    internal IEnumerator LoadBundleDepend(BundleInfo bundleInfo, Action<int, AssetBundle> callback)
    {
      string[] dependencies = manifest.GetDirectDependencies(bundleInfo.fullName); //Pass the name of the bundle you want the dependencies for.
      
      LogTools.Info("Load Bundle Dependencies:"+bundleInfo.name+"=>" + dependencies.Length);
      for (var i = 0; i < dependencies.Length; i++)
      {
        // Debug.LogError("load bundle de:"+dependencies[i]);
        var dependency = dependencies[i];
        var ext = Path.GetExtension(dependency);
        var name = Path.GetFileNameWithoutExtension(dependency);
        var tmp = name.Split('_');
        string dependencyName;
        if (tmp.Length > 1)
        {
          var tt = tmp.ToList();
          tt.RemoveAt(tt.Count-1);
          dependencyName = string.Join('_', tt);
        }
        else
        {
          dependencyName = name;
        }
        if (loadingBundles.Contains(dependencyName))
        {
          LogTools.Warn("Depend Bundle:" + dependencyName);
          continue;
        }
        if (Resource.loadBundleMap.ContainsKey(dependencyName))
        {
          LogTools.Warn("Already Bundle:" + dependencyName);
          continue;
        }
        BundleInfo dependencyInfo = GetBundleInfo(dependencyName);
        if (dependencyInfo == null)
        {
          LogTools.Warn("Miss Bundle:" + dependencyName);
          continue;
        }
        
        loadingBundles.Add(dependencyInfo.name);
        yield return LoadBundleDepend(dependencyInfo, null);
        loadingBundles.Remove(dependencyInfo.name);
        
        string dependencyUri;
        if (dependencyInfo.stream)
        {
          dependencyUri = GetStream(GetName(dependencyInfo.fullName));
        }
        else
        {
          dependencyUri = GetUrl(GetName(dependencyInfo.fullName));
        }
        UnityWebRequest dependencyRequest = UnityWebRequestAssetBundle.GetAssetBundle(dependencyUri, Hash128.Parse(dependencyInfo.version), 0);
        if (!dependencyRequest.isDone)
        {
          yield return dependencyRequest.SendWebRequest();
        }
        
        while (!dependencyRequest.isDone)
        {
          LogTools.Error("Download Bundle:" + dependencyInfo.name+"->"+dependencyRequest.downloadProgress);
          yield return null;
        }
        AssetBundle dependencyBundle = DownloadHandlerAssetBundle.GetContent(dependencyRequest);
        
        // AssetBundle dependencyBundle = AssetBundle.LoadFromFile(dependencyUri.Replace("file://", ""), 0);
        if (dependencyBundle == null)
        {
          LogTools.Error("Error Bundle:" + dependencyInfo.name);
          continue;
        }

        LogTools.Info("Download Bundle:" + dependencyName+":"+dependencyInfo.fullName);
        dependencyInfo.download = true;
        Resource.LoadAllAssetsName(dependencyName, dependencyBundle, ns);

        if (callback != null)
          callback(dependencies.Length - i, dependencyBundle);
      }
      
    }

    internal IEnumerator LoadBundleInline(string bundleName, Action<int, AssetBundle> callback)
    {
      if (Resource.loadBundleMap.ContainsKey(bundleName))
      {
        if (callback != null)
          callback(0, null);
        yield break;
      }
      BundleInfo bundleInfo = GetBundleInfo(bundleName);
      if (bundleInfo == null)
      {
        LogTools.Warn("Miss Bundle:" + bundleName);
        if (callback != null)
          callback(0, null);
        yield break;
      }
      LogTools.Info("Get Bundle:" + bundleName);
      loadingBundles.Add(bundleName);
      yield return LoadBundleDepend(bundleInfo, callback);
      loadingBundles.Remove(bundleName);
      
      string uri;
      if (bundleInfo.stream)
      {
        uri = GetStream(GetName(bundleInfo.fullName));
      }
      else
      {
        uri = GetUrl(GetName(bundleInfo.fullName));
      }
      UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, Hash128.Parse(bundleInfo.version), 0);
      if (!request.isDone)
      {
        yield return request.SendWebRequest();
      }
      while (!request.isDone)
      {
        LogTools.Error("Download Bundle:" + bundleInfo.name+"->"+request.downloadProgress);
        yield return null;
      }
      AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
      // AssetBundle bundle = AssetBundle.LoadFromFile(uri.Replace("file://", ""), 0);
      if (bundle == null)
      {
        LogTools.Error("Error Bundle:" + bundleName);
        if (callback != null)
          callback(0, null);
        yield break;
      }

      LogTools.Info("Download Bundle:" + bundleName+":"+bundleInfo.fullName);
      bundleInfo.download = true;
      Resource.LoadAllAssetsName(bundleName, bundle, ns);

      LogTools.Info("Loaded Bundle:" + bundleName);
      if (callback != null)
        callback(0, bundle);
    }

    // hash的bundle映射
    private BundleInfo GetBundleInfo(string bundleName)
    {
      BundleInfo bundleInfo = null;
      if (!bundles.TryGetValue(bundleName, out bundleInfo))
      {
      }

      return bundleInfo;
    }

    internal static string GetBundleName(string bundleName, string variant)
    {
      bundleName = bundleName.ToLower();
      if (!string.IsNullOrEmpty(variant)) bundleName = bundleName + "." + variant.ToLower();
      return bundleName;
    }
  }
  public class Resource : MonoBehaviour
  {
    public const string LocalNamespace = "Namespace";
    public const string LocalBundle = "Bundle";
    public const string ResourcePath = "Assets/Resources/";
    public const char BundleVariant = '_';
    public static Dictionary<string, AudioClip> audioClipMap = new Dictionary<string, AudioClip>();
    public static Dictionary<string, Texture> textureMap = new Dictionary<string, Texture>();

    // 当前已经加载的namespace
    public static Dictionary<string, NamespaceInfo> namespaceMap = new Dictionary<string, NamespaceInfo>();
    // 下载过的namespace
    public static JSONObject namespaceInfoMap = new JSONObject();

    public static Dictionary<string, AssetBundle> loadBundleMap = new Dictionary<string, AssetBundle>();
    public static Dictionary<int, List<string>> levelBundleMap = new Dictionary<int, List<string>>();
    public static Dictionary<int, Dictionary<string, AssetBundle>> assetLevelMap = new Dictionary<int, Dictionary<string, AssetBundle>>();

    public static Dictionary<int, Dictionary<string, string>> assetAliasMap =
      new Dictionary<int, Dictionary<string, string>>();
    public static Dictionary<int, List<string>> bundleDir = new Dictionary<int, List<string>>();
    public static int bundleLevel = 0;

    // 能否更新App
    public static bool CanUpdateApp
    {
      get
      {
        return Settings.AppInfoPath != "" && inlineStatus && Settings.Update;
      }
    }
    public static bool CanUpdateNamespace
    {
      get
      {
        return inlineStatus && Settings.Update;
      }
    }

    public static bool NamespaceStatus
    {
      get { return Settings.Namespace && inlineStatus; }
    }

    // 是否启用Bundle
    public static bool AssetBundleStatus
    {
      get
      {
        return Settings.AssetBundle && inlineStatus;
      }
    }

    public static bool ILRuntime
    {
      get
      {
        return !Settings.ILRuntimeLocal && inlineStatus;
      }
    }

    // 主Bundle空间
    public static string MainNamespace
    {
      get
      {
        return Settings.ns;
      }
    }
    private static AppMainifestSettings settings;
    public static AppMainifestSettings Settings
    {
      get
      {
        if (settings == null)
          settings = LoadSettings();
#if UNITY_EDITOR || UNITY_SERVER
        // 测试模式是否强制开启Namespace和bundle
        if (settings.EditorTest && !settings.CStatus)
        {
          inlineStatus = true;
        }
#else
        // 兼容模式是否强制关闭Namespace和bundle
        if (settings.CStatus)
        {
          inlineStatus = false;
        }
#endif
        return settings;
      }
    }
#if UNITY_IOS
    internal static string Build = BundleBuild.iOS;
    internal static string sizeField = "iosSize";
#elif UNITY_ANDROID
		internal static string Build = BundleBuild.Android;
    internal static string sizeField = "androidSize";
#elif UNITY_STANDALONE_OSX
		internal static string Build = BundleBuild.OSX;
    internal static string sizeField = "osxSize";
#elif UNITY_STANDALONE_WIN
    internal static string Build = BundleBuild.WIN;
    internal static string sizeField = "winSize";
#elif UNITY_STANDALONE
    internal static string Build = BundleBuild.LINUX;
    internal static string sizeField = "linuxSize";
#endif

    private static bool isNewInstall = false;
#if UNITY_EDITOR || UNITY_SERVER
    // 是否启用
    private static bool inlineStatus = false;

    private static AppInfo info = new AppInfo();
    private static AppInfo localInfo = new AppInfo();
#else
		private static bool inlineStatus = true;

		private static AppInfo info;
		private static AppInfo localInfo;
#endif

    static Resource()
    {
      levelBundleMap[bundleLevel] = new List<string>();
      assetLevelMap[bundleLevel] = new Dictionary<string, AssetBundle>();
      assetAliasMap[bundleLevel] = new Dictionary<string, string>();
      bundleDir[bundleLevel] = new List<string>();
    }

    private static JSONNode LoadManifest(string source)
    {
      var url = Path.GetFileNameWithoutExtension(source);
      var w = Resources.Load<TextAsset>(url);
      if (w == null)
      {
        LogTools.Error("Manifest not exist:" + url);
        return new JSONObject();
      }
      var o = JSONNode.Parse(w.text);
      Resources.UnloadAsset(w);
      return o;
    }

    private static bool VerifyNewInstall(string path)
    {
      if (!File.Exists(path))
      {
        return true;
      }

      // 读取Persistent
      LogTools.Process("Load persistent manifest");
      var oo = JSONNode.LoadFromFile(path);
      LogTools.Info("Manifest:" + oo.ToString());

      var appInfo = new AppInfo();
      appInfo.androidUrl = oo["android"];
      appInfo.iosUrl = oo["ios"];
      appInfo.version = oo["version"];
      appInfo.date = oo["date"];
      if (localInfo.version != appInfo.version || localInfo.date != appInfo.date)
      {
        return true;
      }

      return false;
    }

    private static IEnumerator LoadLocalAppInfo()
    {
      LogTools.Process("Load LocalAppInfo");
      if (Settings.Manifest != "")
      {
        // 判断app清单文件
        var manifest = Path.Combine(Application.persistentDataPath, Settings.Manifest);
        LogTools.Info("Local manifest:" + manifest);

        var o = LoadManifest(Settings.Manifest);
        localInfo = new AppInfo();
        localInfo.androidUrl = o["android"];
        localInfo.iosUrl = o["ios"];
        localInfo.version = o["version"];
        localInfo.date = o["date"];
        if (VerifyNewInstall(manifest))
        {
          LogTools.Process("New install, update manifest:" + o.ToString());
          isNewInstall = true;
          o.SaveToFile(manifest);
          // 删除namespace
          File.Delete(Path.Combine(Application.persistentDataPath, Settings.NamespaceManifest));
        }
      }

      yield return null;
    }
    private static bool VerifyNewUpdate(string path, JSONNode map)
    {
      if (!File.Exists(path))
      {
        return true;
      }

      // 读取Persistent
      LogTools.Process("Load persistent manifest namespace");
      var oo = JSONNode.LoadFromFile(path);
      LogTools.Info("Manifest Namespace:" + oo.ToString());

      var old = oo[Settings.ns];
      var local = map[Settings.ns];
      LogTools.Process("Persistent "+old["date"]+"=> local "+local["date"]);
      if (old["date"] != local["date"])
      {
        return true;
      }

      return false;
    }

    private static IEnumerator LoadLocalNamespaceInfo()
    {
      JSONNode map;
      if (Settings.NamespaceManifest != "")
      {
        // 判断清单文件
        var manifest = Path.Combine(Application.persistentDataPath, Settings.NamespaceManifest);
        LogTools.Process("Local namespace manifest:" + manifest);
        map = LoadManifest(Settings.NamespaceManifest);
        
        if (VerifyNewUpdate(manifest, map))
        {
          map.SaveToFile(manifest);
          LogTools.Process("Copy to Persistent:" + map.ToString());
        }
      }
      else
      {
        map = JSONNode.Parse("{}");
      }
      
      LogTools.Process("Load LocalNamespaceInfo: "+map.ToString());
      namespaceInfoMap = map.AsObject;
      yield return null;
    }

    public static NamespaceInfo LoadNamespace(string ns)
    {
      NamespaceInfo namespaceInfo;
      var result = namespaceMap.TryGetValue(ns, out namespaceInfo);
      if (!result)
      {
        var nsNode = namespaceInfoMap[ns];
        LogTools.Process("Load Namespace("+ns+"):"+nsNode.ToString());
        var nsInfo = nsNode.AsObject;
        namespaceInfo = new NamespaceInfo();
        namespaceInfo.ns = ns;
        namespaceInfo.Version = nsInfo["version"];
        namespaceInfo.size = nsInfo["size"];
        if (nsInfo["local"].AsBool)
        {
          namespaceInfo.stream = true;
        }
        namespaceInfo.bundles = new Dictionary<string, BundleInfo>();
        var bundles = nsInfo["bundles"].AsArray;
        foreach (var bundle in bundles.Children)
        {
          var info = bundle.AsObject;
          var bundleInfo = new BundleInfo
          {
            name = info["name"],
            fullName = info["fullName"],
            version = info["version"],
            stream = info["stream"].AsBool,
            download = info["download"].AsBool,
          };
          Debug.Log("load namespace:"+bundleInfo.name);
          namespaceInfo.bundles.Add(bundleInfo.name, bundleInfo);
        }
        namespaceInfo.files = new Dictionary<string, DownloadInfo>();
        var files = nsInfo["files"].AsArray;
        foreach (var file in files.Children)
        {
          var info = file.AsObject;
          var downloadInfo = new DownloadInfo
          {
            name = info["name"],
            version = info["version"],
            hash = info["hash"],
          };
          namespaceInfo.files.Add(downloadInfo.name, downloadInfo);
        }
        namespaceInfo.url = Path.Combine(Settings.AppInfoPath, localInfo.version, "namespace", namespaceInfo.ns);
        namespaceMap[ns] = namespaceInfo;
      }
      return namespaceInfo;
    }

    private static void UpdateNamespaceInfo(NamespaceInfo namespaceInfo)
    {
      JSONObject nsInfo;
      if (namespaceInfoMap[namespaceInfo.ns].IsNull)
      {
        nsInfo = new JSONObject();
      }
      else
      {
        nsInfo = namespaceInfoMap[namespaceInfo.ns].AsObject;
      }
      nsInfo["version"] = namespaceInfo.Version;
      nsInfo["local"] = namespaceInfo.stream;
      nsInfo["size"] = namespaceInfo.size;

      var bundles = new JSONArray();
      foreach (var kv in namespaceInfo.bundles)
      {
        var bundle = kv.Value;
        var info = new JSONObject();
        info["download"] = bundle.download;
        info["stream"] = bundle.stream;
        info["name"] = bundle.name;
        info["fullName"] = bundle.fullName;
        info["version"] = bundle.version;
        bundles.Add(info);
      }
      nsInfo["bundles"] = bundles;

      var files = new JSONArray();
      foreach (var kv in namespaceInfo.files)
      {
        var file = kv.Value;
        var info = new JSONObject();
        info["name"] = file.name;
        info["version"] = file.version;
        info["hash"] = file.hash;
        files.Add(info);
      }
      nsInfo["files"] = files;
      namespaceInfoMap[namespaceInfo.ns] = nsInfo;
      LogTools.Process("Update namespace map:" + namespaceInfoMap.ToString());

      var manifest = Path.Combine(Application.persistentDataPath, Settings.NamespaceManifest);
      namespaceInfoMap.SaveToFile(manifest);
    }

    private static IEnumerator LoadLastAppInfo()
    {
      info = new AppInfo();
      var url = Path.Combine(Settings.AppInfoPath, Settings.Manifest);
      LogTools.Process("Download app info:" + url);
      WWW w = new WWW(url);
      yield return w;
      if (w.isDone)
      {
        var o = JSONNode.Parse(w.text);
        LogTools.Info("app info:" + w.text);
        if (o != null)
        {
          info.androidUrl = o["android"];
          info.androidSize = o["androidSize"];
          info.iosUrl = o["ios"];
          info.version = o["version"];
          info.date = o["date"];
        }
      }
    }

    private static IEnumerator LoadLastNamespaceInfo(NamespaceInfo namespaceInfo)
    {
      var url = Path.Combine(namespaceInfo.url + ".json");
      LogTools.Process("Download Bundle info:" + url);
      WWW w = new WWW(url);
      yield return w;
      if (w.isDone && w.error == null)
      {
        var o = JSONNode.Parse(w.text);
        LogTools.Info(namespaceInfo.ns + " Namespace info:" + w.text);
        if (o != null)
        {
          namespaceInfo.lastVersion = o["version"];
          namespaceInfo.lastUrl = o["url"];
          namespaceInfo.lastSize = o[sizeField].IsNull ? 0 : o[sizeField].AsInt;
        }
      }
    }

    // 加载版本信息: 是否要更新
    public static IEnumerator InitApp(Action<bool, bool, bool> callback)
    {
      // 读取本地app文件
      yield return LoadLocalAppInfo();
      yield return LoadLocalNamespaceInfo();
      // 初始化本地bundle: 在不支持bundle时，可以作为stream读取器
      var namespaceInfo = LoadNamespace(MainNamespace);

      if (CanUpdateApp)
      {
        yield return LoadLastAppInfo();
        LogTools.Info("App Version: {0} {1} -> {2}", localInfo.version, info.version, localInfo.version != info.version);
        if (localInfo.version != info.version)
        {
          try
          {
            var localDate = DateTime.Parse(localInfo.date);
            var lastDate = DateTime.Parse(info.date);
            LogTools.Info("App Date: {0} {1} -> {2}", lastDate, localDate, lastDate > localDate);
            if (lastDate > localDate)
            {
              // app需要更新
              LogTools.Info("App update:" + localInfo.version + "->" + info.version);
              callback(true, false, true);
              yield break;
            }
          }
          catch (Exception e)
          {
            Debug.LogError(e);
            callback(false, false, false);
            yield break;
          }

        }
      }
      if (AssetBundleStatus)
      {
        yield return namespaceInfo.InitBundle();
      }

      if (CanUpdateNamespace)
      {
        yield return LoadLastNamespaceInfo(namespaceInfo);
        if (namespaceInfo.lastVersion != "" && namespaceInfo.Version != namespaceInfo.lastVersion)
        {
          try
          {
            var localDate = DateTime.Parse(namespaceInfo.Version);
            var lastDate = DateTime.Parse(namespaceInfo.lastVersion);
            LogTools.Info("Main Bundle Date: {0} {1} -> {2}", lastDate, localDate, lastDate > localDate);
            if (lastDate > localDate)
            {
              // 主包需要更新
              LogTools.Info("Main Bundle update:" + namespaceInfo.Version + "->" + namespaceInfo.lastVersion);
              callback(false, true, true);
              yield break;
            }
          }
          catch (Exception e)
          {
            Debug.LogError(e);
            callback(false, false, false);
            yield break;
          }
        }
      }

      callback(false, false, true);
    }

    // 加载namespace信息: 是否要更新
    public static IEnumerator InitNamespace(Action<Dictionary<string, NamespaceInfo>> callback)
    {
      var result = new Dictionary<string, NamespaceInfo>();
      var e = namespaceInfoMap.GetEnumerator();
      while (e.MoveNext())
      {
        var k = (KeyValuePair<string, JSONNode>)e.Current;
        if (k.Key == MainNamespace) continue;
        var nsBundle = LoadNamespace(k.Key);
        if (CanUpdateNamespace)
        {
          yield return LoadLastNamespaceInfo(nsBundle);
        }
        result[k.Key] = nsBundle;
      }
      callback(result);
    }
    // 更新
    public static IEnumerator Update(Action<int, string> callback)
    {
      LogTools.Process("Update MainNamespace");
      yield return UpdateNamespace(MainNamespace, Settings.Auto, callback);
    }

    // 下载或更新namespace
    public static IEnumerator UpdateNamespace(string ns, bool auto, Action<int, string> callback)
    {
      var namespaceInfo = LoadNamespace(ns);

      if (CanUpdateNamespace && namespaceInfo.lastVersion == "")
      {
        yield return LoadLastNamespaceInfo(namespaceInfo);
      }
      if (namespaceInfo.lastVersion != "" && namespaceInfo.Version != namespaceInfo.lastVersion)
      {
        var localDate = DateTime.Parse(namespaceInfo.Version);
        var lastDate = DateTime.Parse(namespaceInfo.lastVersion);
        LogTools.Info("Main Bundle Date: {0} {1} -> {2}", lastDate, localDate, lastDate > localDate);
        if (lastDate > localDate)
        {
          namespaceInfo.Version = namespaceInfo.lastVersion;
          namespaceInfo.url = namespaceInfo.lastUrl;
          namespaceInfo.size = namespaceInfo.lastSize;
          yield return namespaceInfo.Update(auto, callback);
          UpdateNamespaceInfo(namespaceInfo);
        }
      }
      callback(0, "");
    }

    public static bool Exist(string ns)
    {
      return namespaceInfoMap[ns] != null;
    }

    public static NamespaceInfo GetNamespace(string ns = "")
    {
      if (ns == "")
      {
        ns = MainNamespace;
      }

      return LoadNamespace(ns);
    }

    private static AppMainifestSettings LoadSettings()
    {
      AppMainifestSettings loadedSettings = (AppMainifestSettings)Resources.Load("AppMainifestSettings", typeof(AppMainifestSettings));
      if (loadedSettings == null)
      {
        loadedSettings = ScriptableObject.CreateInstance<AppMainifestSettings>();
        LogTools.Error("ResourceSettings is not exist (Resources)");
      }
      return loadedSettings;
    }

    internal static void LoadAllAssetsName(string bundleName, AssetBundle bundle, string ns)
    {
      if (bundle.isStreamedSceneAssetBundle)
      {
        return;
      }
      var allAssets = bundle.GetAllAssetNames();
      if (allAssets.Length == 0)
      {
        LogTools.Warn(bundleName + " all assets is empty");
        return;
      }
      var bundleList = levelBundleMap[bundleLevel];
      if (!bundleList.Contains(bundleName))
      {
        bundleList.Add(bundleName);
      }

      loadBundleMap.Add(bundleName, bundle);
      
      
      var assetsBundle = assetLevelMap[bundleLevel];
      var assetsAlias = assetAliasMap[bundleLevel];
      // 移除根路径
      var rootPath = LocalBundle + "/";
      if (ns != "" && NamespaceStatus)
      {
        rootPath += ns + "/";
      }

      rootPath += bundleName + "/";
      for (var i = 0; i < allAssets.Length; i++)
      {
        assetsBundle.Add(allAssets[i], bundle);
        var name = allAssets[i].Replace(ResourcePath.ToLower(), "").Replace(rootPath.ToLower(), "");
        assetsAlias.Add(name, allAssets[i]);
        var alias = name.Replace(Path.GetExtension(name), "");
        if (!assetsAlias.ContainsKey(alias))
        {
          assetsAlias.Add(alias, allAssets[i]);
        }
        else
        {
          LogTools.Warn("Assets alias has exist: {0}", alias);
        }
        // Debug.Log(name+":"+alias);
      }
    }

    public static IEnumerator Download(string filename, Action<byte[]> callback, string ns = "")
    {
      LogTools.Process("Load File:" + filename);

      if (ns == "")
      {
        ns = MainNamespace;
      }

      var namespaceInfo = LoadNamespace(ns);
      yield return namespaceInfo.Download(filename, callback);
      UpdateNamespaceInfo(namespaceInfo);
    }

    public static IEnumerator LoadBundle(string assetBundleName, string variant, Action<int, AssetBundle> callback, string ns = "")
    {
      // assetBundleName = assetBundleName.ToLower();
      var bundleName = NamespaceInfo.GetBundleName(assetBundleName, variant);
      LogTools.Process("Load Web Bundle:" + assetBundleName+":"+bundleName+":"+AssetBundleStatus);
      AssetBundle bb;
      if (loadBundleMap.TryGetValue(bundleName, out bb))
      {
        LogTools.Process("Hit Bundle:" + bundleName);
        if (callback != null)
          callback(0, bb);
        yield break;
      }
      if (ns == "") ns = MainNamespace;
      if (!AssetBundleStatus)
      {
        if (Settings.AssetBundle)
        {
          var name = "";
          if (Settings.Namespace)
          {
            name += ns + "/";
          }

          name += bundleName;

          if (!bundleDir[bundleLevel].Contains(name))
          {
            bundleDir[bundleLevel].Add(name);
          }
        }
        callback(0, null);
        yield break;
      }

      var namespaceInfo = LoadNamespace(ns);

      if (!namespaceInfo.loaded)
      {
        yield return namespaceInfo.InitBundle();
      }
      yield return namespaceInfo.LoadBundle(assetBundleName, variant, callback);
      UpdateNamespaceInfo(namespaceInfo);
    }

    public static void PushBundle()
    {
      if (!AssetBundleStatus)
      {
        if (Settings.AssetBundle)
        {
          if (!bundleDir.TryGetValue(bundleLevel, out _))
          {
            bundleDir[bundleLevel] = new List<string>();
          }
        }
        return;
      }
      bundleLevel++;
      Debug.LogError("push bundle:"+bundleLevel);
      if (!levelBundleMap.TryGetValue(bundleLevel, out _))
      {
        levelBundleMap[bundleLevel] = new List<string>();
      }
      if (!assetLevelMap.TryGetValue(bundleLevel, out _))
      {
        assetLevelMap[bundleLevel] = new Dictionary<string, AssetBundle>();
      }
      if (!assetAliasMap.TryGetValue(bundleLevel, out _))
      {
        assetAliasMap[bundleLevel] = new Dictionary<string, string>();
      }
    }

    public static void UnloadBundle()
    {
      if (!AssetBundleStatus)
      {
        if (Settings.AssetBundle)
        {
          bundleDir[bundleLevel].Clear();
        }
        return;
      }
      Debug.LogError("unload bundle:"+bundleLevel);
      assetLevelMap[bundleLevel].Clear();
      assetAliasMap[bundleLevel].Clear();

      var bundleList = levelBundleMap[bundleLevel];
      var e = bundleList.GetEnumerator();
      while (e.MoveNext())
      {
        var bundle = loadBundleMap[e.Current];
        if (bundle != null)
        {
          bundle.Unload(false);
          loadBundleMap.Remove(e.Current);
        }
      }
      e.Dispose();
      levelBundleMap[bundleLevel].Clear();
    }

    public static void PopBundle()
    {
      if (bundleLevel == 0)
      {
        return;
      }
      if (!AssetBundleStatus)
      {
        if (Settings.AssetBundle)
        {
          bundleDir[bundleLevel].Clear();
        }
        return;
      }
      Debug.LogError("pop bundle:"+bundleLevel);

      assetLevelMap[bundleLevel].Clear();
      assetAliasMap[bundleLevel].Clear();
      var bundleList = levelBundleMap[bundleLevel];
      var e = bundleList.GetEnumerator();
      while (e.MoveNext())
      {
        var bundle = loadBundleMap[e.Current];
        if (bundle != null)
        {
          bundle.Unload(true);
          loadBundleMap.Remove(e.Current);
        }
      }
      e.Dispose();
      levelBundleMap[bundleLevel].Clear();
      bundleLevel--;
    }

    public static T LoadAsset<T>(string name) where T : UnityEngine.Object
    {
      if (!AssetBundleStatus)
      {
        T result = null;
        // 兼容bundle
        if (Settings.AssetBundle)
        {
          var eb = bundleDir.GetEnumerator();
          while (eb.MoveNext())
          {
            foreach (var dir in eb.Current.Value)
            {
              var tmp = dir + "/" + name;
              result = (T)Resources.Load(LocalBundle + "/"+tmp, typeof(T));
              if (result)
              {
                return result;
              }
              result = (T)Resources.Load(tmp, typeof(T));
              if (result)
              {
                return result;
              }
            }
            //
            // if (result)
            // {
            //   break;
            // }
          }
          eb.Dispose();
          // if (result)
          //   return result;
        }
        result = (T)Resources.Load(name, typeof(T));
        if (result)
        {
          return result;
        }
        LogTools.Warn("Resource Load Local miss: " + name);
        return null;
      }

      // bundle内部都转为了小写
      name = name.ToLower();
      LogTools.Process("Load Assets " + name);
      var e = assetLevelMap.GetEnumerator();
      while (e.MoveNext())
      {
        AssetBundle bundle;
        var result = e.Current.Value.TryGetValue(name, out bundle);
        if (!result)
        {
          string realName;
          result = assetAliasMap[e.Current.Key].TryGetValue(name, out realName);
          if (!result)
          {
            continue;
          }

          bundle = e.Current.Value[realName];
          name = realName;
        }
        LogTools.Process("Resource Load " + name);
        e.Dispose();
        return (T)bundle.LoadAsset(name, typeof(T));
      }
      e.Dispose();


      //            throw new NullReferenceException("Asset:"+name+" not in Bundles");
      return (T)Resources.Load(name, typeof(T));

      // 不在已加载bundle，从Resources中寻找
      return (T)Resources.Load(name, typeof(T));
    }

    public static T Load<T>(string name) where T : UnityEngine.Object
    {
      return LoadAsset<T>(name);
    }

    public static AppInfo GetAppInfo()
    {
      return info;
    }

    public static AppInfo GetLocalAppInfo()
    {
      return localInfo;
    }

    public static bool IsNewInstall()
    {
      return isNewInstall;
    }

    public static Texture GetTexture(string tag)
    {
      if (!textureMap.ContainsKey(tag))
      {
        textureMap.Add(tag, LoadAsset<Texture>("Texture/" + tag));
      }
      return textureMap[tag];
    }

    public static AudioClip GetAudioClip(string tag)
    {
      if (!audioClipMap.ContainsKey(tag))
      {
        audioClipMap.Add(tag, LoadAsset<AudioClip>("Audio/" + tag));
      }
      return audioClipMap[tag];
    }

    public static TextAsset GetOnlineLanguage(string language)
    {
      return LoadAsset<TextAsset>("language/" + language);
    }

    public static TextAsset GetLocalLanguage(string language)
    {
      return LoadAsset<TextAsset>("Language/" + language);
    }

    public static TextAsset GetData(string data, string language=null)
    {
      return LoadAsset<TextAsset>("Data/" + (language != null ? language + "/" : "") + data);
    }

    public static GameObject GetObject(string path)
    {
      return Resources.Load<GameObject>(path);
    }

    public static GameObject[] GetAllObject(string path)
    {
      return Resources.LoadAll<GameObject>(path);
    }
  }
}